﻿using System;
using System.Dynamic;
using System.Linq;
using System.Linq.Expressions;
using System.Xml.Linq;

namespace XEnamel
{
	public class ExpressionToXml
	{
		public static XElement Translate<T>(T expr)
			where T : Expression
		{
			return Visit(expr);
		}

		private static XElement Visit(Expression expr)
		{
			switch (expr.NodeType)
			{
				case ExpressionType.Conditional:
					return Visit(expr as ConditionalExpression);
				case ExpressionType.Goto:
					return Visit(expr as GotoExpression);
				case ExpressionType.PostDecrementAssign:
					return Visit(expr as UnaryExpression);
				case ExpressionType.MultiplyAssign:
				case ExpressionType.GreaterThan:
				case ExpressionType.Equal:
				case ExpressionType.Or:
				case ExpressionType.Assign:
					return Visit(expr as BinaryExpression);
				case ExpressionType.Constant:
					return Visit(expr as ConstantExpression);
				case ExpressionType.Lambda:
					return Visit(expr as LambdaExpression);
				case ExpressionType.Block:
					return Visit(expr as BlockExpression);
				case ExpressionType.Parameter:
					return Visit(expr as ParameterExpression);
				case ExpressionType.Loop:
					return Visit(expr as LoopExpression);
				case ExpressionType.MemberAccess:
					return Visit(expr as MemberExpression);
				case ExpressionType.Call:
					return Visit(expr as MethodCallExpression);
				case ExpressionType.Dynamic:
					return Visit(expr as DynamicExpression);
				default:
					throw new InvalidOperationException(expr.NodeType.ToString());
			}
		}

		private static XElement Visit(ConditionalExpression expr)
		{
			return new XElement(expr.NodeType.ToString(),
				new XElement(expr.NodeType.ToString() + ".Test", Visit(expr.Test)),
				new XElement(expr.NodeType.ToString() + ".IfTrue", Visit(expr.IfTrue)),
				new XElement(expr.NodeType.ToString() + ".IfFalse", Visit(expr.IfFalse)));
		}

		private static XElement Visit(GotoExpression expr)
		{
			return new XElement(expr.NodeType.ToString(),
				new XAttribute("Type", expr.Type),
				new XElement(expr.NodeType.ToString() + ".Target", Visit(expr.Target)),
				new XElement(expr.NodeType.ToString() + ".Value", Visit(expr.Value)));
		}

		private static XElement Visit(UnaryExpression expr)
		{
			return new XElement(expr.NodeType.ToString(),
				new XElement(expr.NodeType.ToString() + ".Operand", Visit(expr.Operand)));
		}

		private static XElement Visit(LoopExpression expr)
		{
			return new XElement(expr.NodeType.ToString(),
				new XElement(expr.NodeType.ToString() + ".BreakLabel", Visit(expr.BreakLabel)),
				new XElement(expr.NodeType.ToString() + ".Body", Visit(expr.Body)));

		}

		private static XElement Visit(LabelTarget lbl)
		{
			return new XElement("LabelTarget", new XAttribute("Name", lbl.Name), new XAttribute("Type", lbl.Type));
		}

		private static XElement Visit(ParameterExpression expr)
		{
			return new XElement(expr.NodeType.ToString(), new XAttribute("Name", expr.Name), new XAttribute("Type", expr.Type));
		}

		private static XElement Visit(LambdaExpression expr)
		{
			return new XElement(expr.NodeType.ToString(),
				new XElement(expr.NodeType.ToString() + ".Parameters", expr.Parameters.Select(Visit)),
				new XElement(expr.NodeType.ToString() + ".Body", Visit(expr.Body)));
		}

		private static XElement Visit(BlockExpression expr)
		{
			return new XElement(expr.NodeType.ToString(),
				new XElement(expr.NodeType.ToString() + ".Variables", expr.Variables.Select(Visit)),
				new XElement(expr.NodeType.ToString() + ".Expressions", expr.Expressions.Select(Visit)));

		}

		private static XElement Visit(ConstantExpression expr)
		{
			return new XElement(expr.NodeType.ToString(), new XAttribute("Type", expr.Type.ToString()), new XAttribute("Value", expr.Value));
		}

		private static XElement Visit(BinaryExpression expr)
		{
			return new XElement(expr.NodeType.ToString(),
				new XElement(expr.NodeType.ToString() + ".Left", Visit(expr.Left)),
				new XElement(expr.NodeType.ToString() + ".Right", Visit(expr.Right)));
		}

		private static XElement Visit(MemberExpression expr)
		{
			return new XElement(expr.NodeType.ToString(),
				new XAttribute("Name", expr.Member.Name),
				//new XAttribute("Type", expr.Type.FullName),
				new XElement(expr.NodeType.ToString() + ".Source", Visit(expr.Expression)));
		}

		private static XElement Visit(MethodCallExpression expr)
		{
			return new XElement(expr.NodeType.ToString(),
				new XAttribute("Name", expr.Method.Name),
				(expr.Object != null)
					? (XObject)new XElement(expr.NodeType.ToString() + ".Object", Visit(expr.Object))
					: new XAttribute("DeclaringType", expr.Method.DeclaringType.FullName),
				new XElement(expr.NodeType.ToString() + ".ArgumentTypes", expr.Arguments.Select(x => new XElement("Type", x.Type.FullName))),
				new XElement(expr.NodeType.ToString() + ".Arguments", expr.Arguments.Select(Visit)));
		}

		private static XElement Visit(DynamicExpression expr)
		{
			if (expr.Binder is BinaryOperationBinder)
			{
				return new XElement(expr.NodeType.ToString(),
					new XAttribute("Operation", (expr.Binder as BinaryOperationBinder).Operation),
					new XAttribute("Type", expr.Type.FullName),
					new XElement(expr.NodeType.ToString() + ".Arguments", expr.Arguments.Select(Visit)));
			}
			else if (expr.Binder is InvokeBinder)
			{
				return new XElement(expr.NodeType.ToString(),
					new XAttribute("Operation", ExpressionType.Invoke),
					new XAttribute("Type", expr.Type.FullName),
					new XElement(expr.NodeType.ToString() + ".Arguments", expr.Arguments.Select(Visit)));
			}
			else
			{
				throw new NotSupportedException();
			}
		}
	}
}